#include "util.h"
#include "log.h"
#include "conf.h"
#include "main.h"

static void data_read_callback(struct bufferevent* bev, void* ctx);
static void connection_event_callback(struct bufferevent* bev, short what, void* ctx);

static int recv_handshake(struct tcp_connection* conn)
{
	char name[256] = { 0 };
	struct sockaddr_in addr = { 0 };
	assert(conn->remote_bev == NULL);

	if (bufferevent_read(conn->local_bev, &addr, sizeof(addr)) != sizeof(addr))
	{
		log_error("read handshake from %s error", get_peer_name(conn->local_bev, name, sizeof(name)));
		return -1;
	}

	log_info("read handshake successfully.");

	bufferevent_setcb(conn->local_bev, data_read_callback, NULL, connection_event_callback, conn);

	conn->remote_bev = tcp_connect(&addr, bufferevent_get_base(conn->local_bev), data_read_callback, NULL, connection_event_callback, conn);
	if (!conn->remote_bev)
		return -1;

	return 0;
}

static void handshake_callback(struct bufferevent* bev, void* ctx)
{
	tcp_connection* conn = (tcp_connection*)ctx;
	if (recv_handshake(conn) != 0)
		destroy_connection(conn);
}

static void data_read_callback(struct bufferevent* bev, void* ctx)
{
	tcp_connection* conn = (tcp_connection*)ctx;
	struct bufferevent* peer = get_peer_bufferevent(bev, conn);

	size_t bytes = relay_data(bev, peer);
	if (bytes > 0)
		enable_relay(conn);
	else
		destroy_connection(conn);
}

static void connection_event_callback(struct bufferevent* bev, short what, void* ctx)
{
	tcp_connection* conn = (tcp_connection*)ctx;	
	log_connection_event(bev, what);
	int err = 1;

	if (what == BEV_EVENT_CONNECTED)
	{
		assert(bev == conn->remote_bev);
		err = 0;
		enable_relay(conn);
	}

	if (err)
		destroy_connection(conn);
}

static void accept_callback(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* addr, int addr_len, void* ctx)
{
	tcp_connection* conn = create_connection(0);

	do
	{
		conn->local_bev = tcp_accept(evconnlistener_get_base(listener), fd, handshake_callback, NULL, connection_event_callback, conn);
		if (!conn->local_bev)
			break;

		bufferevent_enable(conn->local_bev, EV_READ);
		return;
	} while (0);
	
	evutil_closesocket(fd);
	destroy_connection(conn);
}

int main(int argc, char** argv)
{
	return generic_main(true, argc, argv, accept_callback, NULL, NULL);
}
